#include <graphics.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <dos.h>

#define WALL_FRONT	1
#define WALL_RIGHT	2
#define WALL_REAR	4
#define WALL_LEFT	8

/*Wall info is redundant since for a wall there are two cells which know
that the wall is there, except for the maze endings, where only one cell
holds the information of the wall*/
char mazeval[16][16];
char wall[16][16]; //Found by the mouse
char setwall[16][16]; //Set by the user
char trav[16][16];
int curx=0,cury=15;

//Directions
#define F 1
#define R 2
#define B 4
#define L 8

int mouse_face=F;


void save_maze()
{
	FILE *p;
	p = fopen("maze.img","wb");
	fwrite((void *)setwall, sizeof(char), 16*16, p);
	fclose(p);
}

void load_maze()
{
	FILE *p;
	p = fopen("maze.img","rb");
	fread((void *)setwall, sizeof(char), 16*16, p);
	fclose(p);
}

void foil(int x1,int x2,int x3,int x4)
{
	char s[10];
	setcolor(15);
	setfillstyle(SOLID_FILL,0);
	bar(0,470,640,480);
	outtextxy(0,470,itoa(x1,s,10));
	outtextxy(40,470,itoa(x2,s,10));
	outtextxy(80,470,itoa(x3,s,10));
	outtextxy(120,470,itoa(x4,s,10));
}

/*Graphics refresh. After the above values have been set
this function is called to reflect the changes on the screen.*/
void maze()
{
	int i,j;
	char s[10];

	setfillstyle(SOLID_FILL,0);
	bar(0,0,640,20);
	outtextxy(0,0,"Current position:");
	outtextxy(160,0,itoa(curx,s,10));
	outtextxy(200,0,itoa(cury,s,10));

	bar(20,20,340,340);

	setcolor(4);
	setlinestyle(DOTTED_LINE,1,1);
	setfillstyle(SOLID_FILL,1);
	for(j=0;j<=16;j++) {
		for(i=0;i<=16;i++) {
				if(i<16)
					line(i*20+20,j*20+20,i*20+20+20,j*20+20);
				if(j<16)
					line(i*20+20,j*20+20,i*20+20,j*20+20+20);
				if(i < 16 && j < 16) {
					if(trav[j][i])
						bar(i*20+20,j*20+20,i*20+20+20,j*20+20+20);
				}
		}
	}

	setfillstyle(SOLID_FILL,10);
	bar(curx*20+20+1,cury*20+20+1,curx*20+20+19,cury*20+20+19);

	setlinestyle(SOLID_LINE,1,1);
	for(j=0;j<16;j++) {
		for(i=0;i<16;i++) {

			setcolor(4);
			outtextxy(i*20+23,j*20+25,itoa(mazeval[j][i],s,10));

			setcolor(2);
			if(setwall[j][i] & WALL_REAR) {
				line(i*20+20,j*20+40,i*20+20+20,j*20+40);
			}
			if(setwall[j][i] & WALL_LEFT) {
				line(i*20+20,j*20+20,i*20+20,j*20+20+20);
			}
			if(setwall[j][i] & WALL_RIGHT) {
				line(i*20+40,j*20+20,i*20+40,j*20+20+20);
			}
			if(setwall[j][i] & WALL_FRONT) {
				line(i*20+20,j*20+20,i*20+20+20,j*20+20);
			}

			setcolor(15);
			if(wall[j][i] & WALL_REAR) {
				line(i*20+20,j*20+40,i*20+20+20,j*20+40);
			}
			if(wall[j][i] & WALL_LEFT) {
				line(i*20+20,j*20+20,i*20+20,j*20+20+20);
			}
			if(wall[j][i] & WALL_RIGHT) {
				line(i*20+40,j*20+20,i*20+40,j*20+20+20);
			}
			if(wall[j][i] & WALL_FRONT) {
				line(i*20+20,j*20+20,i*20+20+20,j*20+20);
			}


		}
	}

	setcolor(15);
	setfillstyle(SOLID_FILL,0);
	pieslice(480,240,60,120,50);
	pieslice(480,240,330,360,50);
	pieslice(480,240,360,30,50);
	pieslice(480,240,240,300,50);
	pieslice(480,240,150,210,50);
	setcolor(0);
	line(480,240,590,240);
	setcolor(15);


	setfillstyle(SOLID_FILL,9);
	switch(mouse_face) {
		case F:
		pieslice(480,240,60,120,50);
		break;

		case R:
		pieslice(480,240,330,360,50);
		pieslice(480,240,360,30,50);
		setcolor(9);
		line(480,240,530,240);
		setcolor(15);
		break;

		case B:
		pieslice(480,240,240,300,50);
		break;

		case L:
		pieslice(480,240,150,210,50);
		break;
	}
}

/*This function converts the direction wrt to the mouse into absolute
direction for the in-memory arrays*/
int convert_direction(int direction)
{
	int new_dir = 0;
	switch(mouse_face) {
		case F: new_dir = direction;
		break;
		case R: new_dir = direction << 1;
		break;
		case B: new_dir = direction << 2;
		break;
		case L: new_dir = direction << 3;
		break;
	}
	if(!(new_dir & 0xf)) {
		new_dir = (new_dir >> 4) & 0xf;
	}
	return new_dir;
}

int get_new_x(int x,int direction)
{
	switch(direction) {
		case F: return x;
		case B: return x;
		case L: return x-1;
		case R: return x+1;
	}
	return x;
}

int get_new_y(int y,int direction)
{
	switch(direction) {
		case F: return y-1;
		case B: return y+1;
		case L: return y;
		case R: return y;
	}
	return y;
}

int mouse_get_new_x(int x,int direction)
{
	return get_new_x(x,convert_direction(direction));
}

int mouse_get_new_y(int y,int direction)
{
	return get_new_y(y,convert_direction(direction));
}


int validate(int *x,int *y)
{
	int inv=0;
	if((*x) < 0) {
		*x = 0;
		inv = -1;
	}
	if((*y) < 0) {
		*y = 0;
		inv = -1;
	}
	if((*x) > 15) {
		*x = 15;
		inv = -1;
	}
	if((*y) > 15) {
		*y = 15;
		inv = -1;
	}
	return inv;
}

void floodfill()
{
	int i,j;
	for(j=0;j<16;j++) {
		for(i=0;i<16;i++) {
			mazeval[j][i] = abs((i-(7+i/8))) + abs((j-(7+j/8)));
		}
	}
}

void boundary()
{
	int i;
	for(i=0;i<16;i++) {
		setwall[0][i] |= WALL_FRONT;
		setwall[15][i] |= WALL_REAR;
		setwall[i][0] |= WALL_LEFT;
		setwall[i][15] |= WALL_RIGHT;
	}
}

int move_mouse_abs(int direction)
{
	curx = get_new_x(curx,direction);
	cury = get_new_y(cury,direction);
	if(!validate(&curx,&cury)) {
		mouse_face = direction;
		return 0;
	}
	return -1;
}


int move_mouse(int direction)
{
	return move_mouse_abs(convert_direction(direction));
}

void init_vars()
{
	int i,j;
	for(j=0;j<16;j++) {
		for(i=0;i<16;i++) {
			wall[j][i] = 0;
			setwall[j][i] = 0;
			trav[j][i] = 0;
		}
	}
	trav[15][0] = 1;
}


/**********************The mouse code********************************/

int sense_wall(int direction)
{
	switch(direction) {

		case F:
		if(setwall[cury][curx] & WALL_FRONT)
			return 1;
		else {
			if((cury-1) > 0) {
				if(setwall[cury-1][curx] & WALL_REAR)
					return 1;
			}
		}
		return 0;

		case R:
		if(setwall[cury][curx] & WALL_RIGHT)
			return 1;
		else {
			if((curx+1) < 16) {
				if(setwall[cury][curx+1] & WALL_LEFT)
					return 1;
			}
		}
		return 0;

		case B:
		if(setwall[cury][curx] & WALL_REAR)
			return 1;
		else {
			if((cury+1) < 16) {
				if(setwall[cury+1][curx] & WALL_FRONT)
					return 1;
			}
		}
		return 0;

		case L:
		if(setwall[cury][curx] & WALL_LEFT)
			return 1;
		else {
			if((curx-1) > 0) {
				if(setwall[cury][curx-1] & WALL_RIGHT)
					return 1;
			}
		}
		return 0;
	}

}

int mouse_sense(int direction)
{
	return sense_wall(convert_direction(direction));
}


/*Called when the mouse detects wall*/
int detected_wall(int direction)
{
	switch(mouse_face) {

		case F:
		wall[cury][curx] |= WALL_FRONT;
		if((cury-1) >= 0) {
			wall[cury-1][curx] |= WALL_REAR;
		}
		return 0;

		case R:
		wall[cury][curx] |= WALL_RIGHT;
		if((curx+1) <= 15) {
			wall[cury][curx+1] |= WALL_LEFT;
		}
		return 0;

		case B:
		wall[cury][curx] |= WALL_REAR;
		if((cury+1) <= 15) {
			wall[cury+1][curx] |= WALL_FRONT;
		}
		return 0;

		case L:
		wall[cury][curx] |= WALL_LEFT;
		if((curx-1) >= 0) {
			wall[cury][curx-1] |= WALL_RIGHT;
		}
		return 0;
	}
}


int mouse_detected_wall(int direction)
{
	return detected_wall(convert_direction(direction));
}

int is_traversed(int direction)
{
	int p,q;
	p = mouse_get_new_x(curx,direction);
	q = mouse_get_new_y(cury,direction);
	if(!validate(&p,&q)) {
		if(trav[q][p])
			return 1;
	}
	return 0;
}

struct cell {
	int x,y;
	int jn;
};

struct cell stack[20];
int stack_top=0;

void push(int x,int y,int jn)
{
	stack[stack_top].x = x;
	stack[stack_top].y = y;
	stack[stack_top].jn = jn;
}

void pop(int *x,int *y,int *jn)
{
	*x = stack[stack_top].x;
	*y = stack[stack_top].y;
	*jn = stack[stack_top].jn;
}

int ms = 10;
int k = ms;
int auto_enabled = 0;

int main(void)
{
	char x,modified=0;
	int p,q,dir;
	int gdriver = DETECT, gmode, errorcode;
	initgraph(&gdriver, &gmode, "");

	init_vars();

	floodfill();

	boundary();

	maze();

	setcolor(15);
	outtextxy(0,360,"Load maze:'l' Save maze:'s'");
	outtextxy(0,375,"To toggle left,front,right,rear walls : 'a','w','d','x' respectively");
	outtextxy(0,390,"Use arrow keys to change current position");
	outtextxy(0,405,"White walls indicate that they have been detected by the mouse");
	outtextxy(0,420,"'m' starts and stops the mouse algorithm (adjust speed using 'b' and 'n')");
	outtextxy(0,435,"Escape exits");
	outtextxy(440,170,"Mouse face");

	while(1) {
		if(!kbhit()) {
			delay(20);

			if(auto_enabled == 1) {
				if(k-- <= 0) {

					if(!mouse_sense(F) && !is_traversed(F)) {
						move_mouse(F);
						goto moved;
					}

					if(!mouse_sense(R) && !is_traversed(R)) {
						move_mouse(R);
						goto moved;
					}

					if(!mouse_sense(L) && !is_traversed(L)) {
						move_mouse(L);
						goto moved;
					}

					move_mouse(B);

					moved:

					trav[cury][curx] = 1;

					maze();
					k = ms;
				}
			}
		} else {
			x = getch();
			if(x == 27)
				goto out_mazer;
			switch(x) {
				case 'H':
					move_mouse_abs(F);
					break;
				case 'K':
					move_mouse_abs(L);
					break;
				case 'P':
					move_mouse_abs(B);
					break;
				case 'M':
					move_mouse_abs(R);
					break;

				case 'a':
					if(curx>0) {
						setwall[cury][curx] ^= WALL_LEFT;
						if((curx-1) >= 0) {
							setwall[cury][curx-1] ^= WALL_RIGHT;
						}
						modified = 1;
					}
					break;
				case 'd':
					if(curx<15) {
						setwall[cury][curx] ^= WALL_RIGHT;
						if((curx+1) <= 15) {
							setwall[cury][curx+1] ^= WALL_LEFT;
						}
						modified = 1;
					}
					break;
				case 'w':
					if(cury>0) {
						setwall[cury][curx] ^= WALL_FRONT;
						if((cury-1) >= 0) {
							setwall[cury-1][curx] ^= WALL_REAR;
						}
						modified = 1;
					}
					break;
				case 'x':
					if(cury<15) {
						setwall[cury][curx] ^= WALL_REAR;
						if((cury+1) <= 15) {
							setwall[cury+1][curx] ^= WALL_FRONT;
						}
					}
					modified = 1;
					break;
				case 's':
					if(modified) {
						save_maze();
						modified = 0;
					}
					break;
				case 'l':
					load_maze();
					modified = 0;
					break;
				case 'b':
					if(ms > 0)
						ms--;
					break;
				case 'n':
					if(ms < 100)
						ms++;
					break;
				case 'm':
					if(auto_enabled)
						auto_enabled = 0;
					else
						auto_enabled = 1;
					break;
			}

			validate(&curx,&cury);
			maze();

		}

	}
	out_mazer:
	closegraph();
	return 0;
}
